<template>
    <div class="slider">
        <div ref="fontRef" class="font-value">
            <div
                v-for="i in length"
                :key="i"
                class="line"
                :style="{ left: `${((i - 1) / (length - 1)) * 100}%` }"
            ></div>
            <div class="line-common" @mousedown="handleDown"></div>
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'

// 可选长度
const length = ref(5)
// 父元素的 dom
const fontRef = ref<HTMLDivElement>()
// 父元素的几何矩形
const fontRect = ref<DOMRect>()

/**
 * 处理鼠标按下事件
 * @param e 鼠标事件对象
 */
const handleDown = (e: MouseEvent) => {
    e.preventDefault() // 阻止默认行为

    // 获取滑块元素并计算事件相对于滑块容器的偏移量
    const slider = e.target as HTMLDivElement

    // 如果fontRect尚未初始化，则获取并记录fontRef的边界矩形
    if (!fontRect.value) {
        fontRect.value = (
            fontRef.value as HTMLDivElement
        ).getBoundingClientRect()
    }

    // 定义鼠标移动时的回调函数
    const moveCb = (e: MouseEvent) => {
        // 计算鼠标当前位置相对于滑块容器左侧的偏移量
        const moveX = e.clientX - (fontRect.value as DOMRect).left
        // 如果鼠标位置在滑块容器内部，则更新滑块位置
        if (moveX > 0 && moveX < (fontRect.value as DOMRect).width) {
            slider.style.left = `${moveX}px`
        }
    }

    let raf: number | null = null // 用于动画帧请求的ID

    // 使用 requestAnimationFrame 做防抖 减少不在渲染帧上的 left 赋值
    const move = (e: MouseEvent) => {
        // 取消上一帧的请求，避免积压
        if (raf) cancelAnimationFrame(raf)
        raf = requestAnimationFrame(() => {
            moveCb(e)
            raf = null
        })
    }

    // 定义鼠标松开时的处理逻辑
    const up = (e: MouseEvent) => {
        // 根据鼠标位置计算滑块应该显示的百分比位置，并限制在0到100之间
        const left =
            (Math.round(
                ((e.clientX - (fontRect.value as DOMRect).left) /
                    (fontRect.value as DOMRect).width) *
                    (length.value - 1)
            ) /
                (length.value - 1)) *
            100
        slider.style.left = `${left > 100 ? 100 : left < 0 ? 0 : left}%`

        // 移除监听器，避免内存泄露
        document.removeEventListener('mousemove', move)
        document.removeEventListener('mouseup', up)
    }

    // 添加事件监听器以响应鼠标移动和松开
    document.addEventListener('mousemove', move)
    document.addEventListener('mouseup', up)
}
</script>
<style lang="scss" scoped>
.slider {
    $bgc: gray;
    .font-value {
        position: relative;
        display: flex;
        width: 200px;
        height: 2px;
        background-color: $bgc;
        .line,
        .line-common {
            position: absolute;
            top: 1px;
            transform: translate(-50%, -50%);
        }
        .line {
            height: 14px;
            width: 2px;
            background-color: $bgc;
        }
        .line-common {
            $size: 16px;
            width: $size;
            height: $size;
            background-color: #25a610;
            border: 1px solid $bgc;
            border-radius: $size;
        }
    }
}
</style>
